'abuanimation.monkey by Belimoth

'TODO
'	nesting compound animations			'does this work since the restructuring?
'	option to wait until current frame is finished before playing next animation? PlayAnimation(name:String, wait:Bool = True)  OR  QeueAnimation(something)
'	GotoFrame() & GotoRandomFrame()
'	implement units
'	if more than one frame would be stepped through per render, than blur them instead of the current downsampling effect [use architecture in abupostprocess]
'	offsets as vectors so that rotation can be taken into account(?)
'	animations that don't play on their own, but instead are stepped manually to respond to some value (velocity etc.)
'	test looping and qeueing behavior
'	synced animations
'	copying animation sets
'	deprecate reset parameter
'	concantenation of offset and displace arrays for compound animations
'	minimum frame duration property?

Strict
Import mojo
Import abutime
Import abuimage
Import abutoolbox



Public

Interface ISprite
	Method CurrentAnimation:String() Property				'[read-only]
	Method NextAnimation:String() Property					'[read-only]
	Method LoopCount:Int() Property							'[read-only]
	
	Method HandleX:Float() Property
	Method HandleY:Float() Property
	Method SetHandle:Void(x:Float, y:Float)
	
	Method AnimationSheet:AbuAnimationSheet() Property
	Method AnimationSheet:Void(animationSheet:AbuAnimationSheet) Property
	Method SetAnimationSheet:Void(path:String, frameWidth:Int, frameHeight:Int)
	
	Method SetRate:Void(rate:Float)
	
	Method AddAnimation:Void(name:String, sequence:Int[], frameDuration:Int, loop:Bool = True, flipX:Bool = False, flipY:Bool = False)
	Method AddAnimation:Void(name:String, sequence:Int[], frameDuration:Int[], loop:Bool = True, flipX:Bool = False, flipY:Bool = False)
	Method AddAnimation:Void(name:String, start:Int, sequence:Int[], frameDuration:Int, loop:Bool = True, flipX:Bool = False, flipY:Bool = False)
	Method AddAnimation:Void(name:String, start:Int, sequence:Int[], frameDuration:Int[], loop:Bool = True, flipX:Bool = False, flipY:Bool = False)
	Method AddAnimation:Void(name:String, subAnimations:String[], loop:Bool = True, flipX:Bool = False, flipY:Bool = False)
	
	Method PlayAnimation:Void(name:String, rate:Float = 1)
	Method QueueAnimation:Void(name:String, rate:Float = 1)
	Method PlayNextAnimation:Void()
	
	Method OnAnimationLoop:Void(animation:String)
	Method OnAnimationEnd:Void(animation:String)
	
	Method AddOffset:Void(name:String, offsetX:Int[], offsetY:Int[], cumulative:Bool = False)
	Method AddDisplacement:Void(name:String, displaceX:Int[], displaceY:Int[])
	Method _Displace:Void(x:Int, y:Int)
	
	Method Draw:Void(x:Int, y:Int, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1)
	'Method DrawFlipped:Void(x:Int, y:Int, flip:Int = FLIP_X, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1)
End



Class AbuSprite Implements ISprite, IJuggleable

Public
	Global juggler := New Juggler<AbuSprite>
	
	'[read-only]
	Method Active:Bool() Property Return _active End
	
	Method Juggle:Void(timePassed:Int)
		If _currentAnimation = Null Then Return
		'If _currentAnimation._static Then Return			'formerly _done
		_accumulator += timePassed * _currentAnimationRate
		While _accumulator > _currentAnimation._frameDuration[_currentFrame]
			_accumulator -= _currentAnimation._frameDuration[_currentFrame]
			If _StepOnce() Then Return			'unsure about this
		Wend
	End
	
	'[read-only]
	Method CurrentAnimation:String() Property Return _currentAnimation._name End
	Method NextAnimation:String() Property Return _nextAnimation End
	Method LoopCount:Int() Property Return _loopCount End

	'[read-write]
	Method HandleX:Float() Property Return _handleX End
	Method HandleY:Float() Property Return _handleY End
	Method SetHandle:Void(x:Float, y:Float)
		_handleX = x
		_handleY = y
		_animationSheet.SetHandle(x, y)
	End
	
	Method AnimationSheet:AbuAnimationSheet() Property Return _animationSheet End
	Method AnimationSheet:Void(animationSheet:AbuAnimationSheet) Property
		Self._animationSheet = animationSheet
	End
	Method SetAnimationSheet:Void(path:String, frameWidth:Int, frameHeight:Int)
		Local sheetNew := New AbuAnimationSheet(path, frameWidth, frameHeight)
		AnimationSheet(sheetNew)
	End
	
	'[write-only]
	Method SetRate:Void(rate:Float)
		_rate = rate
		_currentAnimationRate = _currentRate * _rate
	End
	
	Method New()
		Initialize()
	End
	
	Method Initialize:Void()
		juggler.Add(Self)
	End
	
	Method Finalize:Void()
		'TODO
	End
	
	Method AddAnimation:Void(name:String, sequence:Int[], frameDuration:Int, loop:Bool = True, flipX:Bool = False, flipY:Bool = False)
		Local frameDurationArray := FillIntArray(sequence.Length, frameDuration)
		AddAnimation(name, sequence, frameDurationArray, loop, flipX, flipY)
	End
	
	Method AddAnimation:Void(name:String, sequence:Int[], frameDuration:Int[], loop:Bool = True, flipX:Bool = False, flipY:Bool = False)
		Local animationNew:= New Animation(name, sequence, frameDuration, loop, flipX, flipY)
		Local first:Bool
		If _animations.IsEmpty() Then first = True
		_animations.Set(name, animationNew)		'replaces an existing animation with the same name
		If first Then PlayAnimation(name)
	End

	Method AddAnimation:Void(name:String, start:Int, sequence:Int[], frameDuration:Int, loop:Bool = True, flipX:Bool = False, flipY:Bool = False)
		Local frameDurationArray:= FillIntArray(sequence.Length, frameDuration)
		AddAnimation(name, start, sequence, frameDurationArray, loop, flipX, flipY)
	End
	
	Method AddAnimation:Void(name:String, start:Int, sequence:Int[], frameDuration:Int[], loop:Bool = True, flipX:Bool = False, flipY:Bool = False)
		Local sequenceAdjusted := New Int[sequence.Length]
		For Local n := 0 Until sequence.Length
			sequenceAdjusted[n] = sequence[n] + start
		Next
		AddAnimation(name, sequenceAdjusted, frameDuration, loop, flipX, flipY)
	End
	
	Method AddAnimation:Void(name:String, subAnimations:String[], loop:Bool = True, flipX:Bool = False, flipY:Bool = False)
		Local subAnimationsArray:Animation[] = New Animation[subAnimations.Length]
		For Local n:Int = 0 Until subAnimations.Length
			subAnimationsArray[n] = _animations.Get(subAnimations[n])
		Next
		
		Local animationNew:= New Animation(name, subAnimationsArray, loop, flipX, flipY)
		_animations.Set(name, animationNew)		'replaces an existing animation with the same name
	End
	
	Method PlayAnimation:Void(name:String, rate:Float = 1)		'DEPRECATE reset parameter?
		_currentAnimation = _animations.Get(name)
		
		'If reset
		_accumulator = 0
		_currentFrame = 0
		_loopCount = 0
		'EndIf
		
		_currentRate = rate
		_currentAnimationRate = rate * Self._rate
		_nextAnimation = ""			'clear qeue
	End
	
	Method PlayNextAnimation:Void()
		PlayAnimation(_nextAnimation, _nextRate)
	End
	
	Method QueueAnimation:Void(name:String, rate:Float = 1)
		_nextAnimation = name
		_nextRate = rate
	End
	
	Method OnAnimationLoop:Void(animation:String)
		'meant to be extended
		If _parent <> Null
			_parent.OnAnimationLoop(animation)
		End
	End
	
	Method OnAnimationEnd:Void(animation:String)
		'meant to be extended
		If _parent <> Null
			_parent.OnAnimationEnd(animation)
		End
	End
	
	Method AddOffset:Void(name:String, offsetX:Int[], offsetY:Int[], cumulative:Bool = False)			'cumulative not implemented yet
		If cumulative
			'TODO
		Endif
		Local animationTemp:Animation = _animations.Get(name)
		animationTemp._offsetX = offsetX
		animationTemp._offsetY = offsetY
		'TODO the offset disappears when an animation is overwritten, perhaps this is not desirable
	End
	
	Method AddDisplacement:Void(name:String, displaceX:Int[], displaceY:Int[])
		Local animationTemp:Animation = _animations.Get(name)
		animationTemp._displaceX = displaceX
		animationTemp._displaceY = displaceY
	End
	
	Method _Displace:Void(x:Int, y:Int)				'messy solution
		If _parent <> Null
			_parent._Displace(x, y)
		Endif
	End
	
	Method Draw:Void(x:Int, y:Int, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1)
		If (_animationSheet = Null) Then Error "animation sheet not defined"
		
		Local flip:Int
		If _currentAnimation._flipX[_currentFrame] Then flip |= FLIP_X
		If _currentAnimation._flipY[_currentFrame] Then flip |= FLIP_Y
		
		_animationSheet.SetHandle(_handleX + _currentAnimation._offsetX[_currentFrame], _handleY + _currentAnimation._offsetY[_currentFrame])			'TODO optimize this
		_animationSheet.DrawFrameFlipped(x, y, _currentAnimation._sequence[_currentFrame], flip, rotation, scaleX, scaleY)
	End
	
	Field _parent:ISprite
	
Private
	Field _active:Bool = True
	
	Field _animationSheet:AbuAnimationSheet
	Field _animations := New StringMap<Animation>
	Field _currentAnimation:Animation, _currentAnimationRate:Float, _currentRate:Float		'this is disorganized
	Field _nextAnimation:String, _nextRate:Float
	Field _handleX:Int, _handleY:Int
	Field _rate:Float = 1
	
	Field _accumulator:Int
	Field _currentFrame:Int
	Field _loopCount:Int
	
	Method _StepOnce:Bool()
		Local returnValue:Bool
		_currentFrame += 1
		If _currentFrame > _currentAnimation._lastFrame
			If _currentAnimation._loop
				_currentFrame = 0
				_loopCount += 1
				_OnLoop()
			Else
				_currentFrame = _currentAnimation._lastFrame
				'_done = True				'unsure about this
				_OnEnd()
				returnValue = True
			EndIf
		EndIf
		_Displace(_currentAnimation._displaceX[_currentFrame], _currentAnimation._displaceY[_currentFrame])
		Return returnValue
	End
	
	Method _OnLoop:Void()		'UNTESTED
		If _nextAnimation <> ""
			PlayNextAnimation()
		Else
			OnAnimationLoop(_currentAnimation._name)
		EndIf
	End
	
	Method _OnEnd:Void()		'UNTESTED
		If _nextAnimation <> ""
			PlayNextAnimation()
		Else
			OnAnimationEnd(_currentAnimation._name)
		Endif
	End
End



Class AbuAnimationSynchronizer
	Global juggler:Juggler<AbuAnimationSynchronizer>
	
	Method Active:Bool() Property Return _active End
	
	Method Juggle:Void(timePassed:Int)
		
	End
	
	Method New(length:Int)
	
	End
	
Private
	Field _active:Bool = true
End		'still trying to think of the best API for this



Function UpdateAnimations:Void(timePassed:Int = -1)		'exposed for now
	If timePassed = -1 Then timePassed = Tick()
	AbuSprite.juggler.Update(timePassed)
End



Private

Class Animation

Public
	Global durationUnit:Int = UNIT_MILLISECONDS			'not implemented
	
	'[read-only]
	Method Name:String() Property Return _name End
	Method Sequence:Int[] () Property Return _sequence End
	
	Method New(name:String, sequence:Int[], frameDuration:Int[], loop:Bool = True, flipX:Bool = False, flipY:Bool = False)
		Self._name = name
		Self._sequence = sequence
			Self._lastFrame = sequence.Length - 1
		Self._frameDuration = frameDuration
		
		Self._loop = loop	
		Self._flipX = FillBoolArray(sequence.Length, flipX)
		Self._flipY = FillBoolArray(sequence.Length, flipY)
		
		Self._offsetX = New Int[sequence.Length]
		Self._offsetY = New Int[sequence.Length]
		
		Self._displaceX = New Int[sequence.Length]
		Self._displaceY = New Int[sequence.Length]
	End
	
	Method New(name:String, subAnimations:Animation[], loop:Bool = True, flipX:Bool = False, flipY:Bool = False)
		Self._name = name
		Self._loop = loop
		
		Self._sequence = New Int[0]
		Self._frameDuration = New Int[0]
		Self._flipX = New Bool[0]
		Self._flipY = New Bool[0]
		'Self._offsetX = New Int[0]
		'Self._offsetY = New Int[0]
		'Self._displaceX = New Int[0]
		'Self._displaceY = New Int[0]
		
		For Local animation:Animation = EachIn subAnimations
			_sequence = ConcantenateIntArrays(_sequence, animation._sequence)
			_frameDuration = ConcantenateIntArrays(_frameDuration, animation._frameDuration)
			_flipX = _flipX.Resize(_flipX.Length + animation._flipX.Length)
			For Local n:= 0 Until animation._flipX.Length
				_flipX[_flipX.Length - n - 1] = Xor(animation._flipX[animation._flipX.Length - n - 1], flipX)
			Next
			_flipY = _flipY.Resize(_flipY.Length + animation._flipY.Length)
			For Local n:= 0 Until animation._flipY.Length
				_flipY[_flipY.Length - n - 1] = Xor(animation._flipY[animation._flipY.Length - n - 1], flipY)
			Next
			'_offsetX = ConcantenateIntArrays(_offsetX, animation._offsetX)
			'ETC
		Next
		
		_lastFrame = _sequence.Length - 1
		
		'TEMPORARY
		_offsetX = New Int[_sequence.Length]
		_offsetY = New Int[_sequence.Length]
		_displaceX = New Int[_sequence.Length]
		_displaceY = New Int[_sequence.Length]
	End
	
Private
	Field _name:String						'via ISprite
	Field _frameDuration:Int[] 				'in milliseconds, for now at least		'expose this as public?
	Field _sequence:Int[]					'expose this as read-only?	'yes
		Field _lastFrame:Int
	Field _loop:Bool						'expose this as public?
	Field _flipX:Bool[]
	Field _flipY:Bool[]
	Field _offsetX:Int[]
	Field _offsetY:Int[]
	Field _displaceX:Int[]
	Field _displaceY:Int[]
End